/* Copyright Andy Mitchell 2007. No warranties */

/*

Two main purposes:
1)
Parse the Gmail Labels box, re-arranging the elements, and extracting all Labels in the account.
Provide functions to retrieve those labels (as new DOM element, array, hash, etc.)

2)
Provide queued interaction with the 'More Actions' drop-down box. 
Used to apply/remove labels to a thread; get current labels on a thread; etc.

*/

function Labels(gtdinboxPage){
	this.gtdinboxPage = gtdinboxPage;
	this.doc = gtdinboxPage.doc;
	this.settings = gtdinboxPage.gtdinboxBrowser.settings;
	this.eventObservers = new EventObservers(this);
	this.eventHandlers = new EventHandlers_Mutator(this);
	this._showHiders = [];
	this._multiToOne = new MultiToOne(gtdinboxPage, 100);
	
	this._labelsEls = {};
	this._labels = null;
	this._labelsHash = null;
	this._labelsArray = null;
	
	this._labelQueue = [];
	
	this._tmpLabelNames = [];
};

Labels.prototype.refresh = function(event){
	// Called by DOM
	this._tmpLabelNames.length = 0;
	this.load();
};

Labels.prototype.load = function(elNav, tmpLabelNames){
	// Parse the labels box; and re-arrange it at the same time
	
try{
	
	this._lastRefresh = (new Date()).valueOf();
	
	// Retrieve the label box
	if( elNav && elNav.nodeType==1 ){
		this.elNav = elNav;
	}
	var result = this.doc.evaluate( "//div[@class='XoqCub XPj4ef']//div[@class='XoqCub C9Pn8c']//div[@class='R7iiN c1norb']//div[@class='nb1Q2b']//div[@class='pvSW6e']", this.elNav, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	this.elBox = result.singleNodeValue;
	if( !tmpLabelNames && this.elBox.firstChild.className=="showhider" ){	// already processed -> stop
		// Just check each entry to see if changed back to prefixed form (& correct it)
		var result = this.doc.evaluate( "//div[@class='XoqCub XPj4ef']//div[@class='XoqCub C9Pn8c']//div[@class='R7iiN c1norb']//div[@class='nb1Q2b']//div[@class='pvSW6e']//div[@class='showhider']//table//span[@class='zD5BAe']", this.elNav, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		for( var i = 0; i < result.snapshotLength; i++ ){
			var el = result.snapshotItem(i);
			for( t in this.settings.labels.types ){
				if( el.innerHTML.indexOf(this.settings.labels.types[t].prefix)==0 ){
					el.innerHTML = el.innerHTML.replace(this.settings.labels.types[t].prefix, "");
				}
			}
		}
		return;
	}
	
	// Stop recursiveness
	if( this.elObserved ) this.eventObservers.stopObserving( this.elObserved, "DOMNodeInserted", this.refresh, false);
	if( this.elObserved ) this.eventObservers.stopObserving( this.elObserved, "mousedown", this.mouseDown, true);
	
	// Destroy old show hiders
	if( !tmpLabelNames ){
		for( var i = 0; i < this._showHiders.length; i++ ){
			this._showHiders[i].destroy();
		}
		this._showHiders.length = 0;
	}
	this._labelsEls = {};
	this._labelsHash = {"_all":{}};
	this._labelsArray = {"_all":[], "_all-misc":[]};

	// Grab the actual original table
	var result = this.doc.evaluate( "//div[@class='XoqCub XPj4ef']//div[@class='XoqCub C9Pn8c']//div[@class='R7iiN c1norb']//div[@class='nb1Q2b']//div[@class='pvSW6e']//table[last()]", this.elNav, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	this.elTable = result.singleNodeValue;

	
	// Extract labels
	var labels = {};	// labels[type][name][subname]	
	var elRows = this.elBox.getElementsByTagName("TR");
	for( var i = 0; i < elRows.length; i++ ){	// Get each label row
		var elRow = elRows[i];
		var elName = elRow.getElementsByTagName("SPAN")[0];
		var labelname = elName.getAttribute("gtdLabelName");
		if( !labelname ) labelname = elName.innerHTML.replace(/\s\(\d+\)$/, "");
		this.processNewLabel(labels, labelname, elRow, elName);
		

	}
	
	if( tmpLabelNames ) for( var i = 0; i < tmpLabelNames.length; i++ ) this.processNewLabel(labels, tmpLabelNames[i].labelname, tmpLabelNames[i].elRow, tmpLabelNames[i].elName);
	
	this._labels = GIPROTOTYPE.clone(labels);
	
	
	// Build new DOM elements
	for( t in this.settings.labels.types ){
		var elLabelsBox = this.doc.createElement("DIV");
			elLabelsBox.className = "labels-group";
			var displayName = this.settings.labels.types[t].name || t;
			elLabelsBox.innerHTML = "<div class='title'>" + displayName + "</div>";
		var elLabels = this.doc.createElement("DIV");
			elLabels.className = "labels";
		elLabelsBox.appendChild( elLabels );
			
		if( labels[t] ){
			var elT = this.doc.createElement("TABLE");
				elT.style.tableLayout = "fixed";
				elT.style.marginBottom = "5px";
			var elBody = this.doc.createElement("TBODY");
			elT.appendChild(elBody);
						
			if( this.settings.labels.types[t].defaults ){	// process in order
				for( d in this.settings.labels.types[t].defaults ){
					var n = this.settings.labels.types[t].defaults[d];
					// If t==Status & d==Finished, perhaps do something more signficant
					if( n && labels[t][n] ){
						this.spawnFromNestedLabels(elBody, elLabels, labels[t][n], tmpLabelNames);
						delete labels[t][n];
					}
				}
			}
			for( n in labels[t] ){
				this.spawnFromNestedLabels(elBody, elLabels, labels[t][n], tmpLabelNames);
			}
			
			if( !tmpLabelNames ){
				var sh = new Showhider(this.gtdinboxPage, t, {doc:this.doc, elParent:this.elBox, elBefore:this.elTable, elContent:elT, title:t} );
					sh.elTitle.style.color = "black";
					sh.elTitle.style.fontWeight = "bold";
				this._showHiders.push( sh );
			}
		}
		this._labelsEls[t] = elLabelsBox;		
	}
		
	this.elObserved = this.elBox.parentNode.parentNode.parentNode;
	this.eventObservers.observe( this.elObserved, "DOMNodeInserted", this.refresh, false);
	this.eventObservers.observe( this.elObserved, "mousedown", this.mouseDown, true);
	
	
}catch(e){
	if( GTDDEBUGMODE ) alert("labels load\n" + e.toString());
}
try{	
	this.eventHandlers.broadcast("refresh-labels");
}catch(e){
	if( GTDDEBUGMODE ) alert("labels load 2\n" + e.toString());
}
		
};
Labels.prototype.processNewLabel = 	function(labels, labelname, elRow, elName){
try{
	var foundType = null;
		
	elName.setAttribute("gtdLabelName", labelname);
	for( t in this.settings.labels.types ){
		if( labelname.indexOf(this.settings.labels.types[t].prefix)==0 ){
			foundType = t;		
			break;
		}
	}
	if( !foundType ) foundType = "Misc";
	if( foundType ){
		if( !labels[t] ) labels[t] = {};
		var noPrefixLabel = labelname.replace(this.settings.labels.types[t].prefix, "");
		/*
		if( t=="Old" ){
			for( t2 in this.settings.labels.types ){
				if( this.settings.labels.types[t2].prefix && noPrefixLabel.indexOf(this.settings.labels.types[t2].prefix)==0 ){
					noPrefixLabel = noPrefixLabel.replace(this.settings.labels.types[t2].prefix, "");
					break;
				}
			}
		}
		*/
		var subs = noPrefixLabel.split("/");
		
		this.processNestedLabels( t, labels[t], subs, labelname, elRow, elName );
		this._labelsHash[labelname] = t;
		
		/*
		var elFilterPopup = this.doc.createElement("IMG");
			elFilterPopup.className = "filter-popuper";
		elFilterPopup.src = "chrome://gtdinbox/content/filter-extend.png";
		elName.parentNode.appendChild(elFilterPopup);
		*/
	}
	if( !this._labelsArray[foundType] ) this._labelsArray[foundType] = [];
	if( !this._labelsHash[foundType] ) this._labelsHash[foundType] = {};
	this._labelsHash[foundType][labelname] = true;
	this._labelsHash["_all"][labelname] = true;
	this._labelsArray[foundType].push(labelname);
	this._labelsArray["_all"].push(labelname);
	if( foundType!="Misc" ) this._labelsArray["_all-misc"].push(labelname);
}catch(e){
	if( GTDDEBUGMODE ) alert("labels processNewLabel\n" + e.toString());
}
};
Labels.prototype.processNestedLabels = function(type, labels, subs, labelname, elRow, elName, depth){
try{
	if( !depth ) depth = 0;
	var n = subs[0];
	if( !labels[n] ) labels[n] = {name:n, children:{}, elRow:null, elName:null, 'depth':depth};
	if( subs.length==1 ){
		labels[n].elRow = elRow;
		elName.innerHTML = elName.innerHTML.replace(/.+?(\s\(\d+\))?$/, n + "$1");
		elName.setAttribute("gtdName", n);
		elName.setAttribute("gtdLabelType", type);
		labels[n].elName = elName;
		labels[n].labelname = labelname;
		elName.style.marginLeft = (depth * 10) + "px";
	}else{
		subs.splice(0,1);
		this.processNestedLabels( type, labels[n].children, subs, labelname, elRow, elName, depth+1);
	}
}catch(e){
	if( GTDDEBUGMODE ) alert("labels processNestedLabels\n" + e.toString());
}
};
Labels.prototype.spawnFromNestedLabels = function(elBody, elLabels, label, tmpLabelNames, depth){
	if( !depth ) depth = 0;
	
	if( label.elRow ){	// There's no real label for this entry in hierachy; create a placeholder
		label.elName.setAttribute("gtdLabelName", label.labelname);
	}else{
		label.elRow = this.doc.createElement("TR");
		label.elRow.innerHTML = "<td><div><span>" + label.name + "</span></div></td>";
		label.elName = label.elRow.getElementsByTagName("SPAN")[0];
		label.elName.style.marginLeft = (label.depth*10) + "px";
		label.labelname = "";
	}
	
	//label.elRow.firstChild.firstChild.style.marginLeft = "14px";
	if( !tmpLabelNames ) elBody.appendChild( label.elRow );
		
	var elLabel = this.doc.createElement("DIV");
		elLabel.innerHTML = label.name;
		elLabel.setAttribute("gtdName", label.name);
		elLabel.setAttribute("gtdLabelName", label.labelname);
		if( depth==0 ) elLabel.setAttribute("gtdRootLabel", "1");
		if( label.labelname ) elLabel.className = "label";
		elLabel.style.marginLeft = (depth*10) + "px";
	elLabels.appendChild( elLabel );
	
	for( c in label.children ){
		this.spawnFromNestedLabels(elBody, elLabels, label.children[c], tmpLabelNames, depth+1);
	}
};

Labels.prototype.addNewLabel = function(labelname){
	// Hack-job. Used if new label created; but Gmail doesn't update fast enough. Called by thread.addLabel.

	var elRow = this.doc.createElement("TR");
	elRow.innerHTML = "<td><div><span>" + labelname + "</span></div></td>";
	var elName = elRow.getElementsByTagName("SPAN")[0];
	
	this._tmpLabelNames.push({'labelname':labelname,'elRow':elRow,'elName':elName});
	this.load(null, this._tmpLabelNames);
	
	this.eventHandlers.broadcast("refresh-labels");
};

Labels.prototype.getLabelsEl = function(type){
	return this._labelsEls[type]? this._labelsEls[type].cloneNode(true) : null;
};
Labels.prototype.getLabels = function(type){
	return this._labels[type] || {};
};
Labels.prototype.getLabelsArray = function(types){
	if( !types ) types = ["_all"];
	else if( typeof types=="string" ){
		types = [types];
	}
	
	if( types.length==1 ){
		return this._labelsArray[types[0]] || [];
	}else{
		var a = [];
		for( var i = 0; i < types.length; i++ ){
			if( this._labelsArray[types[i]] ) a = a.concat( this._labelsArray[types[i]] );
		}
		return a;
	}
};
Labels.prototype.getLabelsHash = function(types){
	if( !types ) types = ["_all"];
	else if( typeof types=="string" ) types = [types];
	
	if( types.length==1 ){
		return this._labelsHash[types[0]] || {};
	}else{
		var hash = {};
		var o = null;
		for( var i = 0; i < types.length; i++ ){
			if( o = this._labelsHash[types[i]] ){
				for( p in o ) if( o.hasOwnProperty(p) ) hash[p] = o[p];
			}
		}
		return hash;
	}
};
Labels.prototype.hasLabel = function(labelname){
	var labels = this._labelsArray["_all"];
	for( var i = 0; i < labels.length; i++ ){
		if( labels[i]==labelname ) return true;
	}
	return false;
};

Labels.prototype.getLabelsElForProjectShortList = function(options){
	// Calculate projects that are likely to be needed
	// Criteria:
	//		Not currently selected
	//		Any projects that match the message body
	//		3 projects from labels recently applied to the names
	//		3 projects from labels recently applied
	// Then show recommended box.
try{
	
	var pprefix = this.settings.labels.types.Projects.prefix;
	
	var rootProjects = {labels:{}, found:0};
	var fAddLabel = function(rootProjects, label){
		var splitName = label.replace(pprefix, "").split("/");
		if( !rootProjects.labels[splitName[splitName.length-1]] ) rootProjects.found++;
		for( var i = 0; i < splitName.length; i++ ){
			if( !rootProjects.labels[splitName[i]] ) rootProjects.labels[splitName[i]] = true;
		}
		/*
		if( !rootProjects.labels[label] ){
			rootProjects.labels[label] = label;	
			rootProjects.found++;
		}
		*/
	};
	
	// Include projects already selected
	if( options.thread && (options.maxSelected||0)>0 ){
		var i = rootProjects.found;
		var selected = options.thread.labels;
		for( var j = 0; j < selected.length; j++ ){
			var l = selected[j];
			if( l.indexOf(pprefix)==0 ) fAddLabel(rootProjects, l);
			if( (rootProjects.found-i)>options.maxSelected ) break;
		}
	}
	
	if( options.thread && (options.maxBody||0)>0 ){
		// Process labels related to message:
		var labels = this.getLabelsArray("Projects");
		if( labels ){
			labels = options.thread.testLabels( labels, pprefix );
			
			var i = rootProjects.found;
			for( var i = 0; i < labels.length; i++ ){
				if( !options.selected || !options.selected[labels[i]] ) fAddLabel(rootProjects, labels[i]);
				if( (rootProjects.found-i)>options.maxBody ) break;
			}
		}
	}
	
	// Retrieve recent and associated with names on this thread
	if( options.thread && (options.maxRecentByName||0)>0 ){
		var names = options.thread.getNames();
		var contacts = [];
		for( var i = 0; i < names.length; i++ ) contacts.push( names[i][1] );
		var projects = this.gtdinboxPage.sp.getRecentProjectsForContacts(contacts);
		
		var i = rootProjects.found; 
		for( var i = 0; i < projects.length; i++ ){
			if( (rootProjects.found-i)<=options.maxRecentByName ) fAddLabel(rootProjects, projects[i]);
		}
		
		//var fRow = function(statement){
		//	if( (rootProjects.found-i)<=options.maxRecentByName ) fAddLabel(rootProjects, statement.getString(0));
		//};
		//this.gtdinboxPage.db.select("SELECT DISTINCT label FROM project_label_activity WHERE " + where + " ORDER BY date DESC LIMIT 20", fRow);
	}
	
	if( (options.maxRecent||0)>0 ){
		// Retrieve recent in general
		var i = rootProjects.found;
		var projects = this.gtdinboxPage.sp.getRecentlyAttachedProjects();
		for( var i = 0; i < projects.length; i++ ){
			if( (rootProjects.found-i)<=options.maxRecent ) fAddLabel(rootProjects, projects[i]);
		}
		//var fRow = function(statement){
		//	if( (rootProjects.found-i)<=options.maxRecent ) fAddLabel(rootProjects, statement.getString(0));
		//};
		//this.gtdinboxPage.db.select("SELECT DISTINCT label FROM project_label_activity ORDER BY date DESC LIMIT 20", fRow);
	}
	
	// Now go through labels, stripping out any 'root' elements that are not allowed
	var el = this.getLabelsEl("Projects");
	

	var els = el.getElementsByTagName("*");
	for( var i = els.length - 1; i>=0; i-- ){
		if( els[i].className=="title" ) els[i].innerHTML = "Recent Projects";	// TODO language
		if( els[i].getAttribute("gtdName") && !rootProjects.labels[els[i].getAttribute("gtdName")] ){
			els[i].parentNode.removeChild(els[i]);
		}
	}
		
	return el;
	
}catch(e){
	if( GTDDEBUGMODE ) alert("labels createShortList\n" + e.toString());
}
};



/*
Labels.prototype.mouseDown = function(event){
	var el = GIPROTOTYPE.element(event);
	if( el.className.indexOf("filter-popuper")==-1 ) return;
	
try{
	GIPROTOTYPE.stop(event);
	
	if( this._filterChild ){
		this._filterChild.destroy();
		if( this._filterChildEl==el ) return;
	}
	var elSpan = el.parentNode.getElementsByTagName("SPAN")[0];
	var labelname = elSpan.getAttribute("gtdLabelName");
	var type = elSpan.getAttribute("gtdLabelType");
	var f = this.settings.filters[type];
	this._filterChild = new FilterPopupUI( this.gtdinboxPage ).create(el.ownerDocument.body, GIPROTOTYPE.searchableLabel(labelname), true, true, f.inlineTypes, f.extendTypes);
	this._filterChild.popup(el,{location:'right'});
	this._filterChildEl = el;
}catch(e){
	if( GTDDEBUGMODE ) alert("labels mousedown\n" + e.toString());
}
};
*/









/* ENVIRONMENT EVENTS */

Labels.prototype.viewChange = function(gmail){
	// Register event handlers with the actioncombo & alert box
	
try{
	
	if( this.elAlertBox ) this.eventObservers.stopObserving( this.elAlertBox, "DOMNodeInserted", this.alertBoxChange, false );
	this.elAlertBox = this.gtdinboxPage.gmailDOM.element("alert-box");
	this.eventObservers.observe( this.elAlertBox, "DOMNodeInserted", this.alertBoxChange, false );
	
	
	if( this._elActionCombo ){
		this.eventObservers.stopObserving( this._elActionCombo, "click", this.actionComboClick, false );
		this.eventObservers.stopObserving( this._elActionCombo, "mousedown", this._restoreScrollTop, true);
	}
	this._elActionCombo = this.gtdinboxPage.gmailDOM.element("action-combo");
	this.eventObservers.observe( this._elActionCombo, "click", this.actionComboClick, false );
	this.eventObservers.observe( this._elActionCombo, "mousedown", this._restoreScrollTop, true);

}catch(e){
	if( GTDDEBUGMODE ) alert("labels viewChange\n" + e.toString());
}
};
Labels.prototype.actionComboClick = function(event){
	var elActionList = this.gtdinboxPage.gmailDOM.element("action-combo-list");
	if( !elActionList ) return;
	
	if( this._elActionCombo ) this.eventObservers.stopObserving( this._elActionCombo, "click", this.actionComboClick, false );
	this._elActionList = elActionList;
	this.eventObservers.observe( this._elActionList, "click", this.actionListClick, true );
};


Labels.prototype.actionListClick = function(event){
	// Should only capture manual clicks - then broadcast it
try{	
	// Find the element clicked 
	var el = GIPROTOTYPE.element(event);
	var label = el.innerHTML.replace(/<.+?>/g,"");
	var safelabel = label.replace(/\"/g, "\\\"");
	var act = el.getAttribute("act") || 0;
	if( !safelabel || !(act==12 || act==13) ){
		return;
	}

	this.eventHandlers.broadcast("actionlist-manual-click", {'label':label, 'act':act, 'safelabel':safelabel});
	return;
}catch(e){
	if( GTDDEBUGMODE ) alert("labels actionComboList\n" + e.toString());
}
};
















/* ENVIRONMENT ACTIONS */

Labels.prototype.labelThread = function(options, qItem){
	// Inform Gmail of change
		
try{
	
	if( !qItem ){	// no callback
		this.gtdinboxPage.unsafeWindow.console.log("request labelThread: " + options.labelname);
		var qItem = {};
		qItem.options = options;
		qItem.start = {'callee':this, 'callback':this.labelThread, args:[qItem.options, qItem]};
		/*
		qItem.start = GIPROTOTYPE.bind(this, function(qItem){
				this.labelThread(qItem.options, qItem);
			});
		*/
		qItem.callback = options.fCallback;
		qItem.test = GIPROTOTYPE.bind(this, function(qItem){
			try{
				this.gtdinboxPage.unsafeWindow.console.log("test labelThread: " + qItem.options.labelname);
				var msg = this.elAlertBox.innerHTML;
				if( msg==this.elAlertBox_LastMsg ) return;
				if( msg.indexOf( "\"" + qItem.options.labelname + "\"" )>-1 ){
					this.gtdinboxPage.unsafeWindow.console.log("^^ test labelThread: " + qItem.options.labelname);
					this.elAlertBox_LastMsg = msg;
					return true;
				}
				}catch(e){
					if( GTDDEBUGMODE ) alert("Labels labelThread test\n" + e.toString());
				}
			});
		this._createQueueItem(qItem);
		return;
	}

	this.gtdinboxPage.unsafeWindow.console.log("commence labelThread: " + options.labelname);
	//this.gtdinboxPage.unsafeWindow.console.log(this._labelQueue);
	//this.gtdinboxPage.unsafeWindow.console.log(this._labelQueueItem);
	
	/*
	if( !this._labelThreadQueue ) this._labelThreadQueue = [];
	if( !this._labelThreadInProgress ) this._labelThreadInProgress = [];
	if( !callback ){
		this._labelThreadQueue.push( options );
	}
	if( this._labelThreadInProgress.length>0 || this._labelThreadQueue.length==0 ){
		this.labelThreadCallback(null, true);
		return;
	}
	options = this._labelThreadQueue[0];
	options.date = (new Date()).valueOf();
	this._labelThreadQueue.splice(0,1);
	this._labelThreadInProgress.push(options);
	this.eventHandlers.broadcast("label-thread-locked");
	*/
	
	var labelname = options.labelname;
	var on = options.on;
	var threads = options.threads;
	
	if( !(threads instanceof Array) ) threads = [threads];

	
	var result = {act:0, elAction:null};
	var fLI = GIPROTOTYPE.bind(this, function(el){
			if( el.innerHTML.replace(/<.+?>/g, "")==labelname ){
				result.act = el.getAttribute("act");
				result.elAction = el;
				if( el.getAttribute("act")=="13" ) return true;	// remove label overrides (remove & add can be on same label)
			}
		});
	this._doMoreAction(fLI, false, true);
	var elAction = result.elAction;
	var act = result.act;
	

	
	if( elAction && ((typeof on=="undefined") || (act==12 && on) || (act==13 && !on)) ){
		if( options.fPreAdd ) options.fPreAdd();
		
		this.gtdinboxPage.unsafeWindow.console.log("click element: " + elAction.innerHTML);
		this.gtdinboxPage.unsafeWindow.console.log(elAction);
		
		GIPROTOTYPE.doMouseEvent(this.doc, elAction, "mouseup");
				
		if( act==12 ){
			for( var i = 0; i < threads.length; i++ ) threads[i].addLabel(labelname);	
		}else{
			for( var i = 0; i < threads.length; i++ ){
				threads[i].removeLabel(labelname);
			}
		}
		options.act = act;
		setTimeout( GIPROTOTYPE.bind(this, function(){this._testQueue()}), 2000 );	// call to make sure deadwood is cleared
	}else{
		this.gtdinboxPage.unsafeWindow.console.log("cancel labelThread: " + qItem.options.labelname);
		options.act = 0;
		if( !elAction ) this._clickActionCombo();	// turn it off
		this._clearQueueItem(qItem);
	}
		
}catch(e){
	if( GTDDEBUGMODE ) alert("labels labelThread\n" + e.toString());
}
	
	
		
};


Labels.prototype.alertBoxChange = function(event){
	this._multiToOne.process("alert-box", GIPROTOTYPE.bind(this, function(){
		this._testQueue();
	}));
	return;
/*	
	var msg = this.elAlertBox.innerHTML;
	if( msg==this._lastAlertMsg ) return;
	this._lastAlertMsg = msg;
	
	this.gtdinboxPage.unsafeWindow.console.log("alert box change:");
	this.gtdinboxPage.unsafeWindow.console.log(msg);
	
	var found = false;
	var labels = this.getLabelsArray();
	for( var i = labels.length-1; i>=0; i-- ){
		if( msg.indexOf("\"" + labels[i] + "\"")>-1 ){	// note the quotes around the label
			found = true;
			this.labelThreadCallback(labels[i]);
		}
	}
	this.labelThreadCallback();
*/
};

/*
Labels.prototype.labelThreadCallback = function(labelname, testToClearOnly){
	// Called after ajax response; and timeout period
	if( !this._labelThreadInProgress ) return;
	
	var callbacks = [];	// queue callbacks due to time senstive processing
	var d = (new Date()).valueOf() - 1000;	// TODO correct to 1000
	for( var i = 0; i < this._labelThreadInProgress.length; i++ ){
		if( (labelname && this._labelThreadInProgress[i].labelname==labelname) ||
			(!labelname && this._labelThreadInProgress[i].date < d)
			){
				for( var j = i; j>=0; j-- ){
					if( this._labelThreadInProgress[j].fCallback ){
						callbacks.push( [this._labelThreadInProgress[j].fCallback, this._labelThreadInProgress[j].act] );
					}
					this._labelThreadInProgress.splice(j,1);
				}
		}
	}
	for( var i = 0; i < callbacks.length; i++ ) 	callbacks[i][0]( callbacks[i][1] );	// process callbacks
	
	// Tell it that it may continue processing
	if( !testToClearOnly ) this.labelThread(null, true);
	
	if( this._labelThreadInProgress.length==0 ) this.eventHandlers.broadcast("label-thread-unlocked");
};
*/

Labels.prototype.createLabel = function(qItem){

	if( !qItem ){	// no callback
		var qItem = {};
		qItem.start = {'callee':this, 'callback':this.createLabel, args:[qItem]};
		/*
		qItem.start = GIPROTOTYPE.bind(this, function(qItem){
				this.createLabel(qItem);
			});
		*/
		this._createQueueItem(qItem);
		return;
	}

	var fLI = GIPROTOTYPE.bind(this, function(el){
			if( el.getAttribute("act")=="14" ){
				GIPROTOTYPE.doMouseEvent(this.doc, el, "mouseup");
				return true;
			}
		});
	this._doMoreAction(fLI);
	
	
	this._clearQueueItem(qItem);
};
Labels.prototype.createLabelAjax = function(labelname){
try{
	if( !labelname ) labelname = prompt("Please enter a new label name:");	// TODO language
	if( !labelname ) return;
	
	var finalCallback = GIPROTOTYPE.bind(this, function(){
			this.addNewLabel(labelname);
		});
	this.gtdinboxPage.gmailDOM.addLabel(labelname, null, finalCallback);
	
	

}catch(e){
	if( GTDDEBUGMODE ) alert("labels createlabelajax\n" + e.toString());
}
};

Labels.prototype.getActiveLabels = function(callee, callback, qItem){
try{
	if( !qItem ){	// no callback
		var qItem = {};
		qItem.options = {'callee':callee, 'callback':callback};	// external
		qItem.start = {'callee':this, 'callback':this.getActiveLabels, args:[null, null, qItem]};
		/*
		qItem.start = GIPROTOTYPE.bind(this, function(qItem){
				this.getActiveLabels(callee, callback, qItem);
			});
		*/
		this._createQueueItem(qItem); 
		return; 
	}
	
	//this.gtdinboxPage.unsafeWindow.console.trace();
	//this.gtdinboxPage.unsafeWindow.console.log(this._labelQueue);
	
	var result = {labels:[]};
	var fLI = GIPROTOTYPE.bind(this, function(el){
			if( el.getAttribute("act")=="13" ){	// remove a label - means its applied
				result.labels.push( el.innerHTML.replace(/<.+?>/g,"") );
			}
		});
	this._doMoreAction(fLI, true);
	//this.gtdinboxPage.unsafeWindow.console.log(result.labels);
		
	// Now callback
	if( qItem.options.callback ) qItem.options.callback.call(qItem.options.callee, result.labels);
	//if( qItem.options.callback ) qItem.options.callback(result.labels);
	//return result.labels;
	
	this._clearQueueItem(qItem);
}catch(e){
	if( GTDDEBUGMODE ) alert("labels getactivelabels\n" + e.toString());
}
};


/*
Labels.prototype.executeActionList = function(f){
	var id = (new Date()).valueOf();
	this._actionListExecId = id;
	
	var fExec = GIPROTOTYPE.bind(this, function(){
			if( id!=this._actionListExecId ) return;
			f();
		});
	
	
};
*/

Labels.prototype._doMoreAction = function(f, forceOff, forceRemainOn){
try{
	this._elActionCombo = this.gtdinboxPage.gmailDOM.element("action-combo");
	this._clickActionCombo();
	this._elActionList = this.gtdinboxPage.gmailDOM.element("action-combo-list");
	if( this._elActionList ){
		this._elActionList.style.display = "none";
		
		var found = false;
		var children = this._elActionList.childNodes;
		for( var i = 0; i < children.length; i++ ){
			var el = children[i];
			if( f(el) ){
				found = true;
				break;
			}
		}
		
		if( (!found || forceOff) && !forceRemainOn ) this._clickActionCombo();	// turn it off
	}
}catch(e){
	if( GTDDEBUGMODE ) alert("Labels _doMoreAction\n" + e.toString());
}
};
Labels.prototype._clickActionCombo = function(){
	this._currentScrollTop = this.gtdinboxPage.gmailDOM.element("document").documentElement.scrollTop;
	GIPROTOTYPE.doMouseEvent(this.doc, this._elActionCombo, "mousedown");
};
Labels.prototype._restoreScrollTop = function(event){
	if( isNaN(this._currentScrollTop) ) return;
	this.gtdinboxPage.gmailDOM.element("window").scrollTo(0, this._currentScrollTop);
	this._currentScrollTop = null;	// make sure it doesn't start jumping after this
};


Labels.prototype._createQueueItem = function(qItem){
try{
	// qItem expected to have {start, test, callback} at least	
	qItem.callee = qItem.callee || this;

	
	this._labelQueue.push(qItem);
	this._testQueue();
}catch(e){
	if( GTDDEBUGMODE ) alert("Labels _createQueueItem\n" + e.toString());
}
};
Labels.prototype._clearQueueItem = function(qItem){
try{
	for( var i = this._labelQueue.length - 1; i>=0; i-- ){
		if( this._labelQueue[i]==qItem ){
			if( this._labelQueue[i].callback ) this._labelQueue[i].callback();
			this._labelQueue.splice(i, 1);
		}
	}
	if( this._labelQueueItem==qItem ){
		this._labelQueueItem = null;
		this._labelQueueLastClear = (new Date()).valueOf();
	}
	this._tryNextQueueItem();
}catch(e){
	if( GTDDEBUGMODE ) alert("Labels _clearQueueItem\n" + e.toString());
}
};
Labels.prototype._testQueue = function(){
try{
	var d = (new Date()).valueOf() - 1500;
	var toClear = [];
	
	var qItem = this._labelQueueItem;
	if( qItem && qItem.test && qItem.test(qItem) ){
		toClear.push(qItem);
	}
	
	for( var i = 0; i < this._labelQueue.length; i++ ){
		var qItem = this._labelQueue[i];
		if( qItem.date < d ){
			toClear.push(qItem);
		}
	}
	for( var i = 0; i < toClear.length; i++ ) this._clearQueueItem(toClear[i]);
	this._tryNextQueueItem();
}catch(e){
	if( GTDDEBUGMODE ) alert("Labels _testQueue\n" + e.toString());
}
};
Labels.prototype._tryNextQueueItem = function(){
try{
	if( !this._labelQueueItem && this._labelQueue.length > 0 ){
		this._labelQueueItem = this._labelQueue[0];
		this._labelQueueItem.date = (new Date()).valueOf();
		//this._labelQueueItem.start(this._labelQueueItem);
		if( this._labelQueueItem.start ){
			var qItem = this._labelQueueItem;
			var d = (new Date()).valueOf() - 300;
			if( (this._labelQueueLastClear||0) > d ){	// Something finished recently
				setTimeout( function(){	
					qItem.start.callback.apply( qItem.start.callee, qItem.start.args );
					}, 300);
			}else{
				qItem.start.callback.apply( qItem.start.callee, qItem.start.args );
			}
		}
	}
}catch(e){
	if( GTDDEBUGMODE ) alert("Labels _tryNextQueueItem\n" + e.toString());
}
};








Labels.prototype.destroy = function(){
	for( var i = 0; i < this._showHiders.length; i++ ){
		this._showHiders[i].destroy();
	}
	this._multiToOne.destroy();
	this._showHiders.length = 0;
	this.eventObservers.destroy();
};